package com.lucky.carcodepro.aspectj;

import com.lucky.carcodepro.common.CurrentContextHolder;
import com.lucky.carcodepro.common.annotation.RateLimiter;
import com.lucky.carcodepro.domain.enums.LimitType;
import com.lucky.carcodepro.exception.RateLimiterException;
import com.lucky.carcodepro.utils.IpUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * RateLimiterAspect
 * <p>
 * 描述：限流注解切面
 *
 * @author Annie
 * @version 2024/10/7 00:26
 */
@Slf4j
@Aspect
@Component
@AllArgsConstructor
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = "redis", matchIfMissing = false)
public class RateLimiterAspect
{
    private final RedissonClient redissonClient;

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint joinPoint, RateLimiter rateLimiter)
    {
        var combineKey = getCombineKey(rateLimiter, joinPoint);
        RRateLimiter rRateLimiter = redissonClient.getRateLimiter(combineKey);
        // 检查限流器是否已经设置速率
        if (!rRateLimiter.isExists())
        {
            // 设置数据限制
            rRateLimiter.trySetRate(
                    RateType.OVERALL, rateLimiter.count(), rateLimiter.time(), RateIntervalUnit.SECONDS
            );
            // 设置限流 key 的过期时间
            redissonClient.getBucket(combineKey).expire(rateLimiter.time(), TimeUnit.SECONDS);
        }

        if (!rRateLimiter.tryAcquire())
        {
            throw new RateLimiterException();
        }
        log.info("限制请求 '{}', 当前请求 '{}', 缓存key '{}'", rateLimiter.count(), rateLimiter.time(), combineKey);
    }

    /**
     * 获取 COMBINE 键
     *
     * @param rateLimiter
     * @param joinPoint
     * @return
     */
    public String getCombineKey(RateLimiter rateLimiter, JoinPoint joinPoint)
    {
        StringBuilder stringBuilder = new StringBuilder(rateLimiter.key());
        switch (rateLimiter.limitType())
        {
            case LimitType.IP -> stringBuilder.append(IpUtils.getIpAddr()).append("-");
            case LimitType.USER -> stringBuilder.append(CurrentContextHolder.getUserId()).append("-");
            case LimitType.DEFAULT -> stringBuilder.append(UUID.randomUUID()).append("-");
            default -> throw new RateLimiterException();
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuilder.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuilder.toString();
    }
}
